﻿namespace Bowlus.Common
{
    using System;
    using System.Linq;
    using System.Threading;

    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.ServiceRuntime;
    using Microsoft.WindowsAzure.StorageClient;
    using Bowlus.Common.Communications;

    public class HostingInfrastructureStore : TableServiceContext
    {
        private static readonly object LockObject = new object();

        private const string HostingInfrastructureStoreTableName = "HostingInfrastructure";

        private static volatile HostingInfrastructureStore instance;

        private HostingInfrastructureStore(CloudStorageAccount account)
            : base(account.TableEndpoint.AbsoluteUri, account.Credentials)
        {
            var tableClient = new CloudTableClient(account.TableEndpoint.AbsoluteUri, account.Credentials);

            tableClient.CreateTableIfNotExist(HostingInfrastructureStoreTableName);

            lock (LockObject)
            {
                var oldDeployments =
                    CreateQuery<HostingInfrastructure>(HostingInfrastructureStoreTableName).Where(
                        c => !c.PartitionKey.Equals(RoleEnvironment.DeploymentId));

                var deleted = false;

                foreach (var hostingInfrastructure in oldDeployments)
                {
                    this.DeleteObject(hostingInfrastructure);
                    deleted = true;
                }

                if (deleted)
                {
                    this.SaveChanges();
                }
            }
        }

        public static bool InfrastructureReady
        {
            get
            {
                var instancesCount = RoleEnvironment.Roles.SelectMany(r => r.Value.Instances).Count();

                var onlineInstances = Instance.HostingInfrastructure.ToList();

                var onlineInstancesCount = onlineInstances.Count();

                var correctNumber = instancesCount == onlineInstancesCount;
                var allStarted = onlineInstances.All(hi => hi.Status.Equals("Started"));
                return correctNumber && allStarted;
            }
        }

        public static HostingInfrastructureStore Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (LockObject)
                    {
                        if (instance == null)
                        {
                            return instance = new HostingInfrastructureStore(Settings.AzureStorageAccount);
                        }
                    }
                }

                return instance;
            }
        }

        public IQueryable<HostingInfrastructure> HostingInfrastructure
        {
            get
            {
                return
                    CreateQuery<HostingInfrastructure>(HostingInfrastructureStoreTableName).Where(
                        c => c.PartitionKey.Equals(RoleEnvironment.DeploymentId));
            }
        }

        public static void UpdateHostingInfraStore(string roleInstanceName, string roleInstanceId, string status)
        {
            var currentInfrastructure = Instance.HostingInfrastructure.ToList();

            if (currentInfrastructure.Any(hi => hi.RowKey.Equals(roleInstanceId)))
            {
                var entity = currentInfrastructure.Find(hi => hi.RowKey.Equals(roleInstanceId));

                if (entity != null)
                {
                    entity.Status = status;
                    entity.StatusChangeTime = DateTime.UtcNow;
                    Instance.UpdateObject(entity);
                    Instance.SaveChanges();
                }
            }
            else
            {
                Instance.AddObject(
                    HostingInfrastructureStoreTableName,
                    new HostingInfrastructure(roleInstanceName, roleInstanceId));
                Instance.SaveChanges();
            }
        }

        public static void AddInstance(string roleName, string roleId)
        {
            var recordedInstances = from instance in Instance.HostingInfrastructure
                                    where instance.RowKey.Equals(roleId)
                                    select instance;

            var hostInstance = recordedInstances.FirstOrDefault<HostingInfrastructure>();

            if (null == hostInstance)
            {
                Instance.AddObject(
                    HostingInfrastructureStoreTableName,
                    new HostingInfrastructure(roleName, roleId));
                Instance.SaveChanges();

                return;
            }

            throw new ApplicationException(string.Format("Instance {0} already exists.", roleId));
        }

        public static void AddInstance()
        {
            var roleInstance = RoleEnvironment.CurrentRoleInstance;

            var recordedInstances = from instance in Instance.HostingInfrastructure
                                    where instance.RowKey.Equals(roleInstance.Id)
                                    select instance;

            var hostInstance = recordedInstances.FirstOrDefault<HostingInfrastructure>();

            if (null == hostInstance)
            {
                Instance.AddObject(
                    HostingInfrastructureStoreTableName,
                    new HostingInfrastructure(roleInstance.Role.Name, roleInstance.Id));
                Instance.SaveChanges();

                return;
            }

            throw new ApplicationException(string.Format("Instance {0} already exists.", roleInstance.Id));
        }

        public static void RemoveInstance()
        {
            var roleInstance = RoleEnvironment.CurrentRoleInstance;

            var recordedInstances = from instance in Instance.HostingInfrastructure
                                    where instance.RowKey.Equals(roleInstance.Id)
                                    select instance;

            var hostInstance = recordedInstances.FirstOrDefault<HostingInfrastructure>();

            if (null != hostInstance)
            {
                Instance.DeleteObject(recordedInstances.First());
                Instance.SaveChanges();

                return;
            }

            throw new ApplicationException(string.Format("Cannot delete instance {0}.", roleInstance.Id));
        }

        public static void SignalInstanceStart(string roleInstanceName, string roleInstanceId)
        {
            UpdateHostingInfraStore(roleInstanceName, roleInstanceId, "Started");
        }

        public static void SignalInstanceStart()
        {
            UpdateHostingInfraStore(RoleEnvironment.CurrentRoleInstance.Role.Name, RoleEnvironment.CurrentRoleInstance.Id, "Started");
        }

        public static void WaitForAppHostToStart()
        {
            while (true)
            {
                var appHostInstances =
                    Instance.HostingInfrastructure.Where(hi => hi.RoleType.Equals(Settings.AppHostRoleName)).ToList();

                if (appHostInstances.All(hi => hi.Status.Equals("Started")) && appHostInstances.Count == RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.Count)
                {
                    return;
                }

                Thread.Sleep(TimeSpan.FromSeconds(5));
            }
        }
    }
}
